﻿using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Principal;
using System.Threading;
using System.Windows.Forms;
using System.Windows.Threading;
namespace TwinHead.Frameworks
{
    /// <summary>
    /// 어플리케이션 수준에 필요한 정보나 도구를 제공합니다.
    /// </summary>
    public static class ApplicationHelper
    {
        /// <summary>
        /// 디자이너 모드입니까?
        /// </summary>
        /// <see ref="http://support.microsoft.com/kb/839202"/>
        public static bool IsDesignerMode
        {
            get { return (System.Reflection.Assembly.GetEntryAssembly() == null); }
        }

        #region 프로세스 보안/권한
        /// <summary>
        /// 관리자 권한으로 실행중입니까?
        /// </summary>
        /// <returns></returns>
        public static bool IsRunAsAdministrator
        {
            get
            {
                WindowsIdentity identity = WindowsIdentity.GetCurrent();

                if (null != identity)
                {
                    WindowsPrincipal principal = new WindowsPrincipal( identity );
                    return principal.IsInRole( WindowsBuiltInRole.Administrator );
                }

                return false;
            }
        }
        #endregion

        #region 윈도우즈 버전
        /// <summary>
        /// 현재 실행중인 운영체제가 Vista 이상의 버전입니까?
        /// </summary>
        public static bool IsVistaHigher
        {
            get { return Environment.OSVersion.Version.Major >= 6; }
        }
        #endregion

        #region 플랫폼 체크 (32비트/64비트)
        [DllImport( "kernel32.dll", SetLastError = true, CallingConvention = CallingConvention.Winapi )]
        [return: MarshalAs( UnmanagedType.Bool )]
        private static extern bool IsWow64Process( [In] IntPtr hProcess, [Out] out bool lpSystemInfo );
        /// <summary>
        /// 64비트 운영체제에서 32비트 환경으로 실행중입니까?
        /// </summary>
        /// <returns></returns>
        private static bool Is32BitProcessOn64BitProcessor()
        {
            bool retVal;

            IsWow64Process( Process.GetCurrentProcess().Handle, out retVal );

            return retVal;
        }
        /// <summary>
        /// 64비트 환경에서 실행중입니까?
        /// </summary>
        /// <returns>
        /// true : 64bit 환경으로 실행하고 있습니다.
        /// false : 32bit 환경으로 실행하고 있습니다.
        /// </returns>
        public static bool Is64Bit()
        {
            if (IntPtr.Size == 8 || (IntPtr.Size == 4 && Is32BitProcessOn64BitProcessor()))
                return true;

            return false;
        }
        #endregion

        #region WPF 종속/특화된 기능
        /// <summary>
        /// Designates a Windows Presentation Foundation application model with added functionalities.
        /// </summary>
        public class WpfApplication : System.Windows.Application
        {
            private static DispatcherOperationCallback exitFrameCallback = new
                                    DispatcherOperationCallback( ExitFrame );

            /// <summary>
            /// Processes all UI messages currently in the message queue.
            /// </summary>
            public static void DoEvents()
            {
                // Create new nested message pump.
                DispatcherFrame nestedFrame = new DispatcherFrame();

                // Dispatch a callback to the current message queue, when getting called,
                // this callback will end the nested message loop.
                // note that the priority of this callback should be lower than the that of UI event messages.
                DispatcherOperation exitOperation = Dispatcher.CurrentDispatcher.BeginInvoke(
                                                      DispatcherPriority.Background, exitFrameCallback, nestedFrame );

                // pump the nested message loop, the nested message loop will
                // immediately process the messages left inside the message queue.
                Dispatcher.PushFrame( nestedFrame );

                // If the "exitFrame" callback doesn't get finished, Abort it.
                if (exitOperation.Status != DispatcherOperationStatus.Completed)
                {
                    exitOperation.Abort();
                }
            }
            /// <summary>
            /// System.Windows.Forms.Application.DoEvents() 의 기능을 래핑하여 제공합니다.
            /// </summary>
            public static void DoEventsAsWinForm()
            {
                System.Windows.Forms.Application.DoEvents();
            }
            private static Object ExitFrame( Object state )
            {
                DispatcherFrame frame = state as DispatcherFrame;

                // Exit the nested message loop.
                frame.Continue = false;
                return null;
            }
        }
        #endregion

        #region Memory Handling
        private static volatile bool _suppressWorkingSetEnabled = true;
        /// <summary>
        /// 작업집합(WorkingSet) 메모리를 새로고침 합니다.
        /// <remarks>
        /// 프로세스의 작업 집합 크기를 늘리면 시스템의 나머지 부분에서 실제 메모리를 가져오게 되므로, <br/>
        /// 시스템의 성능이 저하될 수 있음에 유의해야 합니다
        /// </remarks>
        /// </summary>
        public static void SuppressWorkingSet()
        {
            if (!_suppressWorkingSetEnabled) return;
            try
            {
                Process curProc = Process.GetCurrentProcess();
                curProc.MaxWorkingSet = curProc.MaxWorkingSet;
            }
            catch
            {
                //Some users won't have permission to adjust their working set.                
                _suppressWorkingSetEnabled = false;
            }
        }
        #endregion

        #region Application 중복실행 방지
        /// <summary>
        /// 어플리케이션의 실행을 감시하여, 중복실행을 방지하는 등의 기능을 제공하는 클래스 입니다.
        /// </summary>
        public sealed class SingleInstanceApplication : IDisposable 
        {
            #region Internal Class
            /// <summary>
            /// Win32 API를 제공하는 클래스 입니다.
            /// </summary>
            internal class NativeMethods
            {
                [DllImport( "user32.dll" )]
                public static extern IntPtr FindWindow( string lpClassName, string lpWindowName );

                [DllImport( "user32.dll" )]
                public static extern IntPtr SendMessage( IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam );

                [DllImport( "user32.dll", CharSet = CharSet.Auto )]
                public static extern int PostMessage( HandleRef hwnd, int msg, IntPtr wparam, IntPtr lparam );

                [DllImport( "user32.dll", CharSet = CharSet.Auto, ExactSpelling = true )]
                public static extern bool ShowWindow( HandleRef hWnd, int nCmdShow );

                public const short WM_COPYDATA = 74;

                public struct COPYDATASTRUCT
                {
                    public int dwData;
                    public int cbData;
                    public IntPtr lpData;
                }
            }

            /// <summary>
            /// 어플리케이션 인스턴스와 의사소통을 위한 수단을 제공하는 창 클래스 입니다.
            /// </summary>
            private class SIANativeWindow : NativeWindow
            {
                public SIANativeWindow()
                {
                    CreateParams cp = new CreateParams();
                    cp.Caption = theInstance.uniqeInstanceId;
                    CreateHandle( cp );
                }

                protected override void WndProc( ref Message m )
                {
                    if (m.Msg == NativeMethods.WM_COPYDATA)
                    {
                        NativeMethods.COPYDATASTRUCT data = (NativeMethods.COPYDATASTRUCT)Marshal.PtrToStructure( m.LParam, typeof( NativeMethods.COPYDATASTRUCT ) );
                        object obj = null;
                        if (data.cbData > 0 && data.lpData != IntPtr.Zero)
                        {
                            byte[] buffer = new byte[data.cbData];
                            Marshal.Copy( data.lpData, buffer, 0, buffer.Length );
                            obj = Deserialize( buffer );
                        }
                        theInstance.OnNewInstanceMessage( obj );
                    }
                    else
                        base.WndProc( ref m );
                }
            }
            #endregion

            /// <summary>
            /// 싱글톤 객체
            /// </summary>
            private static SingleInstanceApplication theInstance = new SingleInstanceApplication();

            /// <summary>
            /// 현재 감시중인 어플리케이션과 동일한 인스턴스의 새로운 실행이 감지되면 발생하는 이벤트 입니다.
            /// </summary>
            public static event NewInstanceMessageEventHandler NewInstanceMessage;

            /// <summary>
            /// 어플리케이션을 식별하는데 사용하는 Uniqe id입니다.
            /// </summary>
            private string uniqeInstanceId;

            /// <summary>
            /// 현재 어플리케이션의 인스턴스를 카운팅하는 전역객체
            /// 마지막 어플리케이션 인스턴스가 종료될때, 자동으로 감소합니다.
            /// </summary>
            private Semaphore instanceCounter;

            /// <summary>
            /// 현재 인스턴스가 제일 처음의 인스턴스 인가?
            /// </summary>
            private bool isFirstInstance;

            /// <summary>
            /// 어플리케이션 인스턴스와 의사소통을 위한 객체입니다.
            /// </summary>
            private SIANativeWindow notifcationWindow;

            /// <summary>
            /// 현재 실행을 요청한 어플리케이션이 처음이 아닙니까? ( 즉 1개 이상의 인스턴스가 존재합니까? )
            /// </summary>
            internal bool IsAlreadyExists
            {
                get { return !isFirstInstance; }
            }

            /// <summary>
            /// 생성자 입니다.
            /// </summary>
            private SingleInstanceApplication()
            {
                uniqeInstanceId = "SIA_" + GetAppId();
                instanceCounter = new Semaphore( 0, Int32.MaxValue, uniqeInstanceId, out isFirstInstance );
            }

            /// <summary>
            /// 초기화 합니다.
            /// </summary>
            internal void Init()
            {
                notifcationWindow = new SIANativeWindow();
            }

            /// <summary>
            /// 새로운 인스턴스 생성에 요청에 대한 알림 이벤트를 발생 시킵니다.
            /// </summary>
            /// <param name="message"></param>
            internal void OnNewInstanceMessage( object message )
            {
                if (NewInstanceMessage != null)
                    NewInstanceMessage( this, message );
            }

            /// <summary>
            /// 이전 인스턴스임을 알리는 이벤트를 발생시킵니다.
            /// </summary>
            /// <param name="message"></param>
            /// <returns></returns>
            internal bool NotifyPreviousInstance( object message )
            {
                IntPtr handle = NativeMethods.FindWindow( null, uniqeInstanceId );
                if (handle != IntPtr.Zero)
                {
                    GCHandle bufferHandle = new GCHandle();
                    try
                    {
                        NativeMethods.COPYDATASTRUCT data = new NativeMethods.COPYDATASTRUCT();
                        if (message != null)
                        {
                            byte[] buffer = Serialize( message );
                            bufferHandle = GCHandle.Alloc( buffer, GCHandleType.Pinned );

                            data.dwData = 0;
                            data.cbData = buffer.Length;
                            data.lpData = bufferHandle.AddrOfPinnedObject();
                        }

                        GCHandle dataHandle = GCHandle.Alloc( data, GCHandleType.Pinned );
                        try
                        {
                            NativeMethods.SendMessage( handle, NativeMethods.WM_COPYDATA, IntPtr.Zero, dataHandle.AddrOfPinnedObject() );
                            return true;
                        }
                        finally
                        {
                            dataHandle.Free();
                        }
                    }
                    finally
                    {
                        if (bufferHandle.IsAllocated)
                            bufferHandle.Free();
                    }
                }
                return false;
            }

            /// <summary>
            /// 사용중인 자원을 해제합니다.
            /// </summary>
            public void Dispose()
            {
                instanceCounter.Close();
                if (notifcationWindow != null)
                    notifcationWindow.DestroyHandle();
            }



            /// <summary>
            /// 어플리케이션의 식별용 문자열을 반환합니다.
            /// </summary>
            /// <returns></returns>
            private static string GetAppId()
            {
                return Path.GetFileName( Environment.GetCommandLineArgs()[0] );
            }
            private static object Deserialize( byte[] buffer )
            {
                using (MemoryStream stream = new MemoryStream( buffer ))
                {
                    return new BinaryFormatter().Deserialize( stream );
                }
            }
            private static byte[] Serialize( Object obj )
            {
                using (MemoryStream stream = new MemoryStream())
                {
                    new BinaryFormatter().Serialize( stream, obj );
                    return stream.ToArray();
                }
            }

            /// <summary>
            /// 현재 실행을 요청한 어플리케이션이 처음이 아닙니까? ( 즉 1개 이상의 인스턴스가 존재합니까? )
            /// </summary>
            public static bool AlreadyExists
            {
                get { return theInstance.IsAlreadyExists; }
            }

            /// <summary>
            /// 요청한 어플리케이션이 이미 실행중임을 알리는 이벤트를 발생시킵니다.
            /// <para>지정한 메세지에 해당하는 어플리케이션이 실행하는지에 대하여 검사하고,</para>
            /// <para>이미 실행중이라면, 실행중인 어플리케이션 쪽으로 새로운 어플리케이션이 실행될려고 한다는 </para>
            /// <para>이벤트를 (NewInstanceMessageEventHandler) 발생시킵니다.</para>
            /// </summary>
            /// <param name="message"></param>
            /// <returns>true : 이미 지정한 메세지에 해당하는 어플리케이션이 실행되고 있습니다.</returns>
            public static bool NotifyExistingInstance( object message )
            {
                if (theInstance.IsAlreadyExists)
                {
                    return theInstance.NotifyPreviousInstance( message );
                }
                return false;
            }

            /// <summary>
            /// 요청한 어플리케이션이 이미 실행중임을 알리는 이벤트를 발생시킵니다.
            /// <para>지정한 메세지에 해당하는 어플리케이션이 실행하는지에 대하여 검사하고,</para>
            /// <para>이미 실행중이라면, 실행중인 어플리케이션 쪽으로 새로운 어플리케이션이 실행될려고 한다는 </para>
            /// <para>이벤트를 (NewInstanceMessageEventHandler) 발생시킵니다.</para>
            /// </summary>
            /// <returns>true : 이미 지정한 메세지에 해당하는 어플리케이션이 실행되고 있습니다.</returns>
            public static bool NotifyExistingInstance()
            {
                return NotifyExistingInstance( null );
            }

            /// <summary>
            /// 지정한 핸들을 소유하고 있는 창을 찾아 사용자 화면으로 표시합니다.
            /// </summary>
            /// <param name="hWnd">찾아서 보여줄 창의 핸들</param>
            /// <param name="nCmdShow">
            /// 보여주기 옵션값.
            /// <para>9: 전체창</para>
            /// </param>
            /// <returns></returns>
            public static bool ShowWindow( HandleRef hWnd, int nCmdShow )
            {
                return NativeMethods.ShowWindow( hWnd, nCmdShow );
            }

            /// <summary>
            /// 초기화를 수행합니다.
            /// </summary>
            public static void Initialize()
            {
                theInstance.Init();
            }

            /// <summary>
            /// 종료과정을 수행합니다.
            /// </summary>
            public static void Close()
            {
                theInstance.Dispose();
            }
        }
        #endregion
    }

    /// <summary>
    /// 동일한 ID를 갖는 어플리케이션의 실행을 감지했을때, 기존(이미 실행중인) 어플리케이션에서 수행할 동작을 대리합니다.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="message"></param>
    public delegate void NewInstanceMessageEventHandler( object sender, object message );
}
